package com.qkjia.zqkspringshell.demo;

import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.shell.Input;
import org.springframework.shell.InputProvider;
import org.springframework.shell.Shell;
import org.springframework.shell.jline.InteractiveShellApplicationRunner;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.stereotype.Component;
import sun.tools.jar.resources.jar;

import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.stream.Collectors;

/**
 * pringShell自定义Runner-后台运行单条命令

 * SpringShell 应用启动时, 会自动创建两个ApplicationRunner组件:
 * ScriptShellApplicationRunner 和 InteractiveShellApplicationRunner,
 * 其中ScriptShellApplicationRunner 用来支持启动直接运行脚本方式, InteractiveShellApplicationRunner
 * 用来支持交互式启动方式. 当我们需要新增一种运行方式时, 那么可以通过自定义ApplicationRunner
 * 实现. 深入了解ScriptShellApplicationRunner 和 InteractiveShellApplicationRunner的逻辑,
 * 可阅读笔者的下一篇博客.

 */
@ShellComponent
public class ZqkRunner {
//    SpringShell应用默认运行方式
//1.1 交互式运行
    /**
     * $ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar
     * # 省略启动信息...
     *
     * shell:>add 2 3
     * 5
     * shell:>exit
     */

//    1.2 脚本运行
   /* $ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar
    # 省略启动信息...

            5
            2
            4*/

//    1.3 笔者期望新增运行方式
/*    $ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar
# 省略启动信息...

            3
            7*/


//    2. 自定义ApplicationRunner
//    自定义Input, 实现Input接口, InputProvider.readInput()方法需要返回一个Input类型的值
//    自定义InputProvider, 实现InputProvider接口, Shell.run()方法会调用InputProvider.readInput()获取执行名
//    自定义ApplicationRunner, 实现ApplicationRunner接口, 因为容器初始化完成之后会执行所有ApplicationRunner
//    的run方法
//    设定ApplicationRunner的优先级, 笔者设定位于默认两个ApplicationRunner 直接, 这个很关键, 直接影响核心代码的编写.
//            2.1 自定义Input
//    SpringShell 源码提供了ParsedLineInput, 但是类权限为包级别, 笔者访问不到. 因此复制源码自成一类.
    /**
     * @Description: 自定义行解析, 复制的底层代码: org.springframework.shell.jline.ParsedLineInput
     * @author: zongf
     * @date: 2019-01-28 11:18
     */
    class MyParsedLineInput implements Input {

        private final ParsedLine parsedLine;

        MyParsedLineInput(ParsedLine parsedLine) {
            this.parsedLine = parsedLine;
        }

        @Override
        public String rawText() {
            return parsedLine.line();
        }

        @Override
        public List<String> words() {
            return sanitizeInput(parsedLine.words());
        }

//        static
        List<String> sanitizeInput(List<String> words) {
            words = words.stream()
                    .map(s -> s.replaceAll("^\\n+|\\n+$", ""))
                    .map(s -> s.replaceAll("\\n+", " "))
                    .collect(Collectors.toList());
            return words;
        }
    }

//自定义InputProvider
//        Shell.run 方法会循环调用InputProvider的readInput()方法, 当readInput()方法返回为null时, 终止循环
//        笔者使用Queue结构来存储启动参数中所有的命令, 执行一条命令移除一条命令, 命令执行完之后返回null.
/**
 * @Description: 读取命令
 * @author: zongf
 * @date: 2019-01-28 11:19
 */
class MyInputProvider implements InputProvider {

    // 存储要执行的所有命令
    private Queue<String> commands;

    private final Parser parser;

    public MyInputProvider(Parser parser, Queue<String> commands) {
        this.parser = parser;
        this.commands = commands;
    }

    @Override
    public Input readInput() {
        String command = commands.poll();
        if (command == null) {
            // return null 时退出应用
            return null;
        }else {
            ParsedLine parsedLine = parser.parse(command, command.length());
            return new MyParsedLineInput(parsedLine);
        }
    }
}
/*
        2.3 自定义ApplicationRuuner
        自定义实现ApplicationRunner 接口的实现类
        将自定义ApplicationRunner 注册为Spring的一个组件, 即使用@Component修饰
设置自定义Runner运行顺序, 保证运行顺序为: ScriptShellApplicationRunner > 自定义Runner > InteractiveShellApplicationRunner, 使用@Order 限制. 只有这样能让脚本参数和命令参数共存.
        延迟注入内置属性: Parser, Shell, enviroment*/
/**
 * @Description: 自定义命令Runner, 启动应用后直接执行多条命令, 执行后结束
 * @author: zongf
 * @date: 2019-01-28 09:58
 */
@Component
@Order(InteractiveShellApplicationRunner.PRECEDENCE - 50)  // order 越小, 越先执行
public class MyCommandsRunner implements ApplicationRunner {

    @Lazy
    @Autowired
    private Parser parser;

    @Lazy
    @Autowired
    private Shell shell;

    @Lazy
    @Autowired
    private ConfigurableEnvironment environment;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //过滤掉所有@开头的参数, @开头的参数交给ScriptShellApplicationRunner 处理
        List<String> cmds = args.getNonOptionArgs().stream()
                .filter(s -> !s.startsWith("@"))
                .collect(Collectors.toList());

        //如果启动参数中, 命令不为空, 则执行
        if (cmds != null && cmds.size() > 0) {
            // 关闭交互式应用: 确保关闭应用后, 直接退出应用, 不停留在交互式窗口中
            InteractiveShellApplicationRunner.disable(environment);

            // 将所有命令存储到队列中
            Queue<String> queue = new PriorityQueue<>();
            queue.addAll(cmds);

            // 执行命令
            shell.run(new MyInputProvider(parser, queue));
        }
    }
}

 /*   测试
    新增自定义ApplicationRunner 之后, SpringShell应用便有了四种运行方式

3.1 测试直接运行命令
    $ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar "add 2 3"
    Welcom to :
    __  _____  _______ __  __________    __
   /  |/  /\ \/ / ___// / / / ____/ /   / /
  / /|_/ /  \  /\__ \/ /_/ / __/ / /   / /
            / /  / /   / /___/ / __  / /___/ /___/ /___
/_/  /_/   /_//____/_/ /_/_____/_____/_____/
    Version: 0.0.1-SNAPSHOT
    Author: zongf
    Date: 2019-01-26

            # 应用启动之后, 直接执行了add 命令, 命令执行之后, 退出应用程序
5
        3.2 测试直接运行脚本
    $ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar @/tmp/zongf/script
    Welcom to :
    __  _____  _______ __  __________    __
   /  |/  /\ \/ / ___// / / / ____/ /   / /
  / /|_/ /  \  /\__ \/ /_/ / __/ / /   / /
            / /  / /   / /___/ / __  / /___/ /___/ /___
/_/  /_/   /_//____/_/ /_/_____/_____/_____/
    Version: 0.0.1-SNAPSHOT
    Author: zongf
    Date: 2019-01-26

            # 应用启动之后, 直接运行脚本中定义的命令
5

        3.3 测试混合运行
    需要注意的是, 会先执行所有的脚本,从才会执行所有命令. 因为ScriptShellApplicationRunner 的优先级在自定义Runner优先级之前.

    $ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar @/tmp/zongf/script "add 20 30" @/tmp/zongf/script
    Welcom to :
    __  _____  _______ __  __________    __
   /  |/  /\ \/ / ___// / / / ____/ /   / /
  / /|_/ /  \  /\__ \/ /_/ / __/ / /   / /
            / /  / /   / /___/ / __  / /___/ /___/ /___
/_/  /_/   /_//____/_/ /_/_____/_____/_____/
    Version: 0.0.1-SNAPSHOT
    Author: zongf
    Date: 2019-01-26

            # 执行第一个脚本

        2
        4
        # 执行第二个脚本

        2
        4
        # 执行自定义命令



        3.4 测试交互式运行
    zongf@zongf-E570 spring-shell $ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar
    Welcom to :
    __  _____  _______ __  __________    __
   /  |/  /\ \/ / ___// / / / ____/ /   / /
  / /|_/ /  \  /\__ \/ /_/ / __/ / /   / /
            / /  / /   / /___/ / __  / /___/ /___/ /___
/_/  /_/   /_//____/_/ /_/_____/_____/_____/

    Version: 0.0.1-SNAPSHOT
    Author: zongf
    Date: 2019-01-26

            # 应用启动之后, 处于交互式窗口之中
    shell:>add 2 3
            5
    shell:>*/


}




